/**
 * ServiceServer
 * 
 * 31/12/2013
 * 
 * Copyright GPL
 */
package com.jtmproject.bluetooth;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;

/**
 * This class does all the work for setting up and managing Bluetooth
 * connections with other devices. It has a thread that listens for
 * incoming connections, a thread for connecting with a device, and a
 * thread for performing data transmissions when connected.
 * 
 * @author admin
 * @thanks Google
 */
public class ServiceServer {

	private static final String SERVER_NAME = "Pong";

	//Unique UUID for this application
	private static final UUID MY_UUID_SECURE =
			UUID.fromString("fa87c0d0-afac-11de-8a39-0800200c9a66");

	private final BluetoothAdapter adapter;
	private final Handler handler;
	private AcceptThread secureAcceptThread;
	private ConnectedThread connectedThread;
	private int state;

	// Constants that indicate the current connection state
	//we're doing nothing
	public static final int STATE_NONE = 0;       
	//now listening for incoming connections
	public static final int STATE_LISTEN = 1;    
	//now initiating an outgoing connection
	public static final int STATE_CONNECTING = 2; 
	//now connected to a remote device
	public static final int STATE_CONNECTED = 3;  

	/**
	 * constructor
	 * @param context 
	 * @param handler 
	 */
	public ServiceServer(Handler handler) {
		adapter = BluetoothAdapter.getDefaultAdapter();
		state = STATE_NONE;
		this.handler = handler;
	}

	/** 
	 * set the current state 
	 * @param state
	 */
	private synchronized void setState(int state) {
		this.state = state;

		handler.obtainMessage(
				Server.MESSAGE_STATE_CHANGE, state, -1).sendToTarget();
	}

	/**
	 * return the current connection state. 
	 * */
	public synchronized int getState() {
		return state;
	}

	/**
	 * starts the connection
	 * */
	public synchronized void start() {

		setState(STATE_LISTEN);

		if (secureAcceptThread == null) {
			secureAcceptThread = new AcceptThread();
			secureAcceptThread.start();
		}
	}

	/**
	 * start the ConnectedThread to begin managing a Bluetooth connection
	 * @param socket  The BluetoothSocket on which the connection was made
	 * @param device  The BluetoothDevice that has been connected
	 */
	public synchronized void connected(
			BluetoothSocket socket, BluetoothDevice device) {

		connectedThread = new ConnectedThread(socket);
		connectedThread.start();

		Message msg = handler.obtainMessage(Server.MESSAGE_DEVICE_NAME);
		Bundle bundle = new Bundle();
		bundle.putInt(Server.DEVICE_NAME, 0);
		msg.setData(bundle);
		handler.sendMessage(msg);

		setState(STATE_CONNECTED);

	}

	/**
	 * stop all threads
	 */
	public synchronized void stop() {

		if (connectedThread != null) {
			connectedThread = null;
		}

		if (secureAcceptThread != null) {
			secureAcceptThread.cancel();
			secureAcceptThread = null; 
		}

		setState(STATE_NONE);
	}

	/** 
	 * write to the ConnectedThread in an unsynchronized manner
	 * @param out The bytes to write
	 */
	public void write(byte[] out) {
		ConnectedThread r;

		synchronized (this) {
			if (state != STATE_CONNECTED) return;
			r = connectedThread;
		} 

		r.write(out);
	}


	/**
	 * indicate that the connection was lost and notify the UI Activity.
	 */
	private void connectionLost() {
		// Send a failure message back to the Activity
		Message msg = handler.obtainMessage(Server.MESSAGE_TOAST);
		Bundle bundle = new Bundle();
		bundle.putString(Server.TOAST, "Device connection was lost");
		msg.setData(bundle);
		handler.sendMessage(msg);

		//start the service over to restart listening mode
		this.start();
	}

	/**
	 * this thread runs while listening for incoming connections. It behaves
	 * like a server-side client. It runs until a connection is accepted
	 * (or until cancelled).
	 * 
	 * @author Javier Tejedor
	 */
	private class AcceptThread extends Thread {
		
		private final BluetoothServerSocket mServerSocket;

		/**
		 * constructor
		 */
		public AcceptThread() {
			BluetoothServerSocket tmp = null;

			//create a new listening server socket
			try {
				tmp = adapter.listenUsingRfcommWithServiceRecord(
						SERVER_NAME, MY_UUID_SECURE);

			} catch (IOException e) {

			}
			mServerSocket = tmp;
		}

		/* (non-Javadoc)
		 * @see java.lang.Thread#run()
		 */
		@Override
		public void run() {
			
			setName("AcceptThread"); 

			BluetoothSocket socket = null;

			try {

				socket = mServerSocket.accept();
				
			} catch (IOException e) {

			}

			//if a connection was accepted
			if (socket != null) {
				synchronized (ServiceServer.this) {
					connected(socket, socket.getRemoteDevice());
					switch (state) {
					case STATE_LISTEN:
						break;
					case STATE_CONNECTING:
						break;
					case STATE_NONE:
						break;
					case STATE_CONNECTED:
						break;
					}
				}
			}

		}

		/**
		 * cancel the socket
		 */
		public void cancel() {
			try {
				mServerSocket.close();
			} catch (IOException e) {

			}
		}
	}

	/**
	 * this thread runs during a connection with a remote device.
	 * It handles all incoming and outgoing transmissions.
	 * 
	 * @author Javier Tejedor
	 */
	private class ConnectedThread extends Thread {

		private final BluetoothSocket mSocket;
		private final InputStream mInStream;
		private final OutputStream mOutStream;

		/**
		 * constructor
		 * @param socket
		 */
		public ConnectedThread(BluetoothSocket socket) {

			this.mSocket = socket;
			InputStream tmpIn = null;
			OutputStream tmpOut = null;

			// Get the BluetoothSocket input and output streams
			try {

				tmpIn = socket.getInputStream();
				tmpOut = socket.getOutputStream();

			} catch (IOException e) {

			}

			mInStream = tmpIn;
			mOutStream = tmpOut;

		}

		/* (non-Javadoc)
		 * @see java.lang.Thread#run()
		 */
		@Override
		public void run() {
			byte[] buffer = new byte[1024];
			int bytes;

			//keep listening to the InputStream while connected
			while (true) {
				try {
					
					bytes = mInStream.read(buffer);
					handler.obtainMessage(
							Server.MESSAGE_READ, 
							bytes, 
							-1, 
							buffer).sendToTarget();
					
				} catch (IOException e) { 

					connectionLost();
					ServiceServer.this.start();
					break;
				}
			}
		}

		/**
		 * Write to the connected OutStream.
		 * @param buffer  The bytes to write
		 */
		public void write(byte[] buffer) {
			try {
				mOutStream.write(buffer);

				// Share the sent message back to the UI Activity
				handler.obtainMessage(Server.MESSAGE_WRITE, -1, -1, buffer)
				.sendToTarget();
			} catch (IOException e) {

			}
		}

		/**
		 * this cancel the socket
		 */
		public void cancel() {
			try {
				mSocket.close();
			} catch (IOException e) {

			}
		}
	}
}